﻿package com._public._utils 
{
	/**
	 * 对象与字符互转
	 * ClassEncoder.encode(new SkinData)
	 * @author WZH (shch8.com)
	 */
	public class ClassEncoder 
	{
		/**
		 * 对象转字符
		 * @param	o
		 * @return
		 */
		public static function encode( o:Object ):String {
			var encoder:Encoder = new Encoder( o );
			return encoder.getString();
		}
		/**
		 * 字符转对象
		 * @param	s
		 * @return
		 */
		public static function decode( s:String ):* {
			var decoder:Decoder = new Decoder( s )
			return decoder.getValue();
		}
	}
}

import flash.utils.describeType;
class Encoder{
		private var jsonString:String;
		public function Encoder(value:*) 
		{
			
			jsonString = convertToString( value );
		}

		public function getString():String {
			return jsonString;
		}
		
		private function convertToString( value:* ):String {
			
			if ( value is String ) {
				
	
				return escapeString( value as String );
				
			} else if ( value is Number ) {
				
				return isFinite( value as Number) ? value.toString() : "null";

			} else if ( value is Boolean ) {
				
				return value ? "true" : "false";

			} else if ( value is Array ) {
			
				return arrayToString( value as Array );
			
			} else if ( value is Object && value != null ) {
			
				return objectToString( value );
			}
            return "null";
		}
		

		private function escapeString( str:String ):String {

			var s:String = "";
	
			var ch:String;
	
			var len:Number = str.length;
			
			for ( var i:int = 0; i < len; i++ ) {
			
				ch = str.charAt( i );
				switch ( ch ) {
				
					case '"':	
						s += "\\\"";
						break;

						
					case '\\':	
						s += "\\\\";
						break;
						
					case '\b':	
						s += "\\b";
						break;
						
					case '\f':	
						s += "\\f";
						break;
						
					case '\n':	
						s += "\\n";
						break;
						
					case '\r':	
						s += "\\r";
						break;
						
					case '\t':	
						s += "\\t";
						break;
						
					default:	
						
						if ( ch < ' ' ) {
							var hexCode:String = ch.charCodeAt( 0 ).toString( 16 );

							var zeroPad:String = hexCode.length == 2 ? "00" : "000";

							s += "\\u" + zeroPad + hexCode;
						} else {
						
							s += ch;
							
						}
				}
				
			}	
						
			return "\"" + s + "\"";
		}
		

		private function arrayToString( a:Array ):String {
			var s:String = "";

			for ( var i:int = 0; i < a.length; i++ ) {

				if ( s.length > 0 ) {

					s += ","
				}
				s += convertToString( a[i] );	
			}

			return "[" + s + "]";
		}
		
		private function objectToString( o:Object ):String
		{

			var s:String = "";
			
			var classInfo:XML = describeType( o );
			if ( classInfo.@name.toString() == "Object" )
			{

				var value:Object;
				
				for ( var key:String in o )
				{
					value = o[key];
					
					if ( value is Function )
					{

						continue;
					}
					if ( s.length > 0 ) {
						s += ","
					}
					
					s += escapeString( key ) + ":" + convertToString( value );
				}
			}
			else
			{
				for each ( var v:XML in classInfo..*.( name() == "variable" || name() == "accessor" ) )
				{

					if ( s.length > 0 ) {
						s += ","
					}
					
					s += escapeString( v.@name.toString() ) + ":" 
							+ convertToString( o[ v.@name ] );
				}
				
			}
			
			return "{" + s + "}";
		}
}

class Decoder {

		private var value:*;
		
		private var tokenizer:GbTokenizer;
		
		private var token:GbToken;
		

		public function Decoder( s:String ) {
			
			tokenizer = new GbTokenizer( s );
			
			nextToken();
			value = parseValue();
		}

		public function getValue():* {
			return value;
		}
		

		private function nextToken():GbToken {
			return token = tokenizer.getNextToken();
		}

		private function parseArray():Array {

			var a:Array = new Array();
			

			nextToken();
			
			
			if ( token.type == GbTokenType.RIGHT_BRACKET ) {
				
				return a;
			}
			
		
			while ( true ) {
				
				a.push ( parseValue() );
			
			
				nextToken();
				
				if ( token.type == GbTokenType.RIGHT_BRACKET ) {
					
					return a;
				} else if ( token.type == GbTokenType.COMMA ) {
					
					nextToken();
				} else {
					tokenizer.parseError( "Expecting ] or , but found " + token.value );
				}
			}
            return null;
		}

		private function parseObject():Object {

			var o:Object = new Object();
						

			var key:String
			
			nextToken();
			
			if ( token.type == GbTokenType.RIGHT_BRACE ) {
		
				return o;
			}
			

			while ( true ) {
			
				if ( token.type == GbTokenType.STRING ) {

					key = String( token.value );
					
				
					nextToken();
					
					
					if ( token.type == GbTokenType.COLON ) {
						
						
						nextToken();
						o[key] = parseValue();	
						
					
						nextToken();
						
						
						if ( token.type == GbTokenType.RIGHT_BRACE ) {
				
							return o;
							
						} else if ( token.type == GbTokenType.COMMA ) {
						
							nextToken();
						} else {
							tokenizer.parseError( "Expecting } or , but found " + token.value );
						}
					} else {
						tokenizer.parseError( "Expecting : but found " + token.value );
					}
				} else {
					tokenizer.parseError( "Expecting string but found " + token.value );
				}
			}
            return null;
		}
		

		private function parseValue():Object
		{
			if ( token == null )
			{
				tokenizer.parseError( "Unexpected end of input" );
			}
					
			switch ( token.type ) {
				case GbTokenType.LEFT_BRACE:
					return parseObject();
					
				case GbTokenType.LEFT_BRACKET:
					return parseArray();
					
				case GbTokenType.STRING:
				case GbTokenType.NUMBER:
				case GbTokenType.TRUE:
				case GbTokenType.FALSE:
				case GbTokenType.NULL:
					return token.value;

				default:
					tokenizer.parseError( "Unexpected " + token.value );
					
			}
            return null;
		}
}
class GbToken {
		private var _type:int;
		private var _value:Object;
		public function GbToken( type:int = -1, value:Object = null ) {
			_type = type;
			_value = value;
		}
		
		public function get type():int {
			return _type;	
		}
		
		public function set type( value:int ):void {
			_type = value;	
		}

		public function get value():Object {
			return _value;	
		}
		
		public function set value ( v:Object ):void {
			_value = v;	
		}
}
 class GbTokenizer {
	

		private var obj:Object;
		

		private var gbString:String;
		

		private var loc:int;
		

		private var ch:String;
		


		public function GbTokenizer( s:String ) {
			gbString = s;
			loc = 0;
			
			nextChar();
		}
		

		public function getNextToken():GbToken {
			var token:GbToken = new GbToken();
			

			skipIgnored();
						
			switch ( ch ) {
				
				case '{':
					token.type = GbTokenType.LEFT_BRACE;
					token.value = '{';
					nextChar();
					break
					
				case '}':
					token.type = GbTokenType.RIGHT_BRACE;
					token.value = '}';
					nextChar();
					break
					
				case '[':
					token.type = GbTokenType.LEFT_BRACKET;
					token.value = '[';
					nextChar();
					break
					
				case ']':
					token.type = GbTokenType.RIGHT_BRACKET;
					token.value = ']';
					nextChar();
					break
				
				case ',':
					token.type = GbTokenType.COMMA;
					token.value = ',';
					nextChar();
					break
					
				case ':':
					token.type = GbTokenType.COLON;
					token.value = ':';
					nextChar();
					break;
					
				case 't': 
					var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
					
					if ( possibleTrue == "true" ) {
						token.type = GbTokenType.TRUE;
						token.value = true;
						nextChar();
					} else {
						parseError( "Expecting 'true' but found " + possibleTrue );
					}
					
					break;
					
				case 'f': 
					var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
					
					if ( possibleFalse == "false" ) {
						token.type = GbTokenType.FALSE;
						token.value = false;
						nextChar();
					} else {
						parseError( "Expecting 'false' but found " + possibleFalse );
					}
					
					break;
					
				case 'n': 
				
					var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
					
					if ( possibleNull == "null" ) {
						token.type = GbTokenType.NULL;
						token.value = null;
						nextChar();
					} else {
						parseError( "Expecting 'null' but found " + possibleNull );
					}
					
					break;
					
				case '"': 
					token = readString();
					break;
					
				default: 
					if ( isDigit( ch ) || ch == '-' ) {
						token = readNumber();
					} else if ( ch == '' ) {

						return null;
					} else {						

						parseError( "Unexpected " + ch + " encountered" );
					}
			}
			
			return token;
		}
		
		private function readString():GbToken {

			var token:GbToken = new GbToken();
			token.type = GbTokenType.STRING;
			

			var string:String = "";
			
			nextChar();
			
			while ( ch != '"' && ch != '' ) {
								
				if ( ch == '\\' ) {

					nextChar();
					
					switch ( ch ) {
						
						case '"': 
							string += '"';
							break;
						
						case '/':	
							string += "/";
							break;
							
						case '\\':	
							string += '\\';
							break;
							
						case 'b':	
							string += '\b';
							break;
							
						case 'f':	
							string += '\f';
							break;
							
						case 'n':
							string += '\n';
							break;
							
						case 'r':	
							string += '\r';
							break;
							
						case 't':	
							string += '\t'
							break;
						
						case 'u':

							var hexValue:String = "";
							
							for ( var i:int = 0; i < 4; i++ ) {

								if ( !isHexDigit( nextChar() ) ) {
									parseError( " Excepted a hex digit, but found: " + ch );
								}
								hexValue += ch;
							}
							
							string += String.fromCharCode( parseInt( hexValue, 16 ) );
							
							break;
					
						default:

							string += '\\' + ch;
						
					}
					
				} else {
					string += ch;
					
				}
				nextChar();
				
			}
			if ( ch == '' ) {
				parseError( "Unterminated string literal" );
			}
			
			nextChar();
			
			token.value = string;
			
			return token;
		}

		private function readNumber():GbToken {
			var token:GbToken = new GbToken();
			token.type = GbTokenType.NUMBER;
			
			var input:String = "";
			
			if ( ch == '-' ) {
				input += '-';
				nextChar();
			}
			
			if ( !isDigit( ch ) )
			{
				parseError( "Expecting a digit" );
			}
			
			if ( ch == '0' )
			{
				input += ch;
				nextChar();
				

				if ( isDigit( ch ) )
				{
					parseError( "A digit cannot immediately follow 0" );
				}
			}
			else
			{
				while ( isDigit( ch ) ) {
					input += ch;
					nextChar();
				}
			}
			
			if ( ch == '.' ) {
				input += '.';
				nextChar();
				
				if ( !isDigit( ch ) )
				{
					parseError( "Expecting a digit" );
				}
				
				while ( isDigit( ch ) ) {
					input += ch;
					nextChar();
				}
			}

			if ( ch == 'e' || ch == 'E' )
			{
				input += "e"
				nextChar();

				if ( ch == '+' || ch == '-' )
				{
					input += ch;
					nextChar();
				}
				
				if ( !isDigit( ch ) )
				{
					parseError( "Scientific notation number needs exponent value" );
				}
							
				while ( isDigit( ch ) )
				{
					input += ch;
					nextChar();
				}
			}
			
			var num:Number = Number( input );
			
			if ( isFinite( num ) && !isNaN( num ) ) {
				token.value = num;
				return token;
			} else {
				parseError( "Number " + num + " is not valid!" );
			}
            return null;
		}

		private function nextChar():String {
			return ch = gbString.charAt( loc++ );
		}
		
		private function skipIgnored():void
		{
			var originalLoc:int;

			do
			{
				originalLoc = loc;
				skipWhite();
				skipComments();
			}
			while ( originalLoc != loc );
		}

		private function skipComments():void {
			if ( ch == '/' ) {
				nextChar();
				switch ( ch ) {
					case '/': 
						
						do {
							nextChar();
						} while ( ch != '\n' && ch != '' )
						
						nextChar();
						
						break;
					
					case '*':


						nextChar();
						
				
						while ( true ) {
							if ( ch == '*' ) {
								
								nextChar();
								if ( ch == '/') {
									
									nextChar();
									break;
								}
							} else {
								
								nextChar();
							}
							
							if ( ch == '' ) {
								parseError( "Multi-line comment not closed" );
							}
						}

						break;
					
					default:
						parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
				}
			}
			
		}
		
		
		private function skipWhite():void {
			
			while ( isWhiteSpace( ch ) ) {
				nextChar();
			}
			
		}
		
		private function isWhiteSpace( ch:String ):Boolean {
			return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );
		}
		private function isDigit( ch:String ):Boolean {
			return ( ch >= '0' && ch <= '9' );
		}
		private function isHexDigit( ch:String ):Boolean {

			var uc:String = ch.toUpperCase();

			return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) );
		}

		public function parseError( message:String ):void {
			throw new GbParseError( message, loc, gbString );
		}
	}
class GbTokenType {
		public static const UNKNOWN:int = -1;
		
		public static const COMMA:int = 0;
		
		public static const LEFT_BRACE:int = 1;
		
		public static const RIGHT_BRACE:int = 2;
		
		public static const LEFT_BRACKET:int = 3;
		
		public static const RIGHT_BRACKET:int = 4;
		
		public static const COLON:int = 6;
		
		public static const TRUE:int = 7;
		
		public static const FALSE:int = 8;
		
		public static const NULL:int = 9;
		
		public static const STRING:int = 10;
		
		public static const NUMBER:int = 11;
	}
class GbParseError extends Error 	{
	
		private var _location:int;
		
		private var _text:String;

		public function GbParseError( message:String = "", location:int = 0, text:String = "") {
			super( message );
			name = "GbParseError";
			_location = location;
			_text = text;
		}

		public function get location():int {
			return _location;
		}
		public function get text():String {
			return _text;
		}
	}